/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2010  BMW Car IT GmbH. All rights reserved.
 *  Copyright (C) 2011 Google, Inc. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

/*
 * OpenVPN plugin built on top of vpn support.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/in.h>

#include <glib.h>

#define CONNMAN_API_SUBJECT_TO_CHANGE
#include <connman/plugin.h>
#include <connman/dbus.h>
#include <connman/provider.h>
#include <connman/inet.h>
#include <connman/ipconfig.h>
#include <connman/log.h>
#include <connman/task.h>

#ifdef ENABLE_NSS
#include "nss.h"
#endif

#include "vpn.h"

#define	_DBG_VPN(fmt, arg...)	DBG(DBG_VPN, fmt, ## arg)

#define	MAXDNS		10		/* max DNS servers per lease */
#define	MAXDOMAINS	10		/* max DNS search domains per lease */

enum openvpn_state {
	OV_STATE_UNKNOWN,
	OV_STATE_CONNECTING,
	OV_STATE_WAIT,
	OV_STATE_AUTH,
	OV_STATE_GET_CONFIG,
	OV_STATE_ASSIGN_IP,
	OV_STATE_ADD_ROUTES,
	OV_STATE_CONNECTED,
	OV_STATE_RECONNECTING,
	OV_STATE_RESOLVE,
	OV_STATE_EXITING
};

static DBusConnection *connection;

static const char *kLSBReleaseFilename = "/etc/lsb-release";
static const char *kChromeOSReleaseName = "CHROMEOS_RELEASE_NAME";
static const char *kChromeOSReleaseVersion = "CHROMEOS_RELEASE_VERSION";

static char *platform_name;
static char *platform_version;

struct mgmt_server_data {
	struct connman_provider *provider;
	char *vpnhost;
	struct sockaddr_in addr;
	GIOChannel *channel;
	guint watch;
	char *username;
	char *password;
	char *state_id;
	enum openvpn_state state;
};

struct openvpn_data {
	struct mgmt_server_data *server;
	struct connman_ipaddress ipaddr;
	in_addr_t vpn_addr;
	in_addr_t gw_addr;
	uint32_t gw_index;
	char *tls_auth_file;
};

struct ov_route {
	char *host;
	char *netmask;
	char *gateway;
};

static void destroy_route(gpointer user_data)
{
	struct ov_route *route = user_data;

	g_free(route->host);
	g_free(route->netmask);
	g_free(route->gateway);
	g_free(route);
}

static void ov_provider_append_routes(gpointer key, gpointer value,
					gpointer user_data)
{
	struct ov_route *route = value;
	struct connman_provider *provider = user_data;

	connman_provider_append_route(provider, route->host, route->netmask,
					route->gateway);
}

static struct ov_route *ov_route_lookup(const char *key, const char *prefix_key,
					GHashTable *routes)
{
	unsigned long idx;
	const char *start;
	char *end;
	struct ov_route *route;

	if (g_str_has_prefix(key, prefix_key) == FALSE)
		return NULL;

	start = key + strlen(prefix_key);
	idx = g_ascii_strtoull(start, &end, 10);

	if (idx == 0 && start == end) {
		connman_error("%s: string conversion failed %s",
		    __func__, start);
		return NULL;
	}

	route = g_hash_table_lookup(routes, GINT_TO_POINTER(idx));
	if (route == NULL) {
		route = g_try_new0(struct ov_route, 1);
		if (route == NULL) {
			connman_error("%s: out of memory", __func__);
			return NULL;
		}

		g_hash_table_replace(routes, GINT_TO_POINTER(idx),
						route);
	}

	return  route;
}

static void ov_append_route(const char *key, const char *value, GHashTable *routes)
{
	struct ov_route *route;

	/*
	 * OpenVPN pushes routing tupples (host, nw, gw) as several
	 * environment values, e.g.
	 *
	 * route_gateway_2 = 10.242.2.13
	 * route_netmask_2 = 255.255.0.0
	 * route_network_2 = 192.168.0.0
	 * route_gateway_1 = 10.242.2.13
	 * route_netmask_1 = 255.255.255.255
	 * route_network_1 = 10.242.2.1
	 *
	 * The hash table is used to group the separate environment
	 * variables together. It also makes sure all tupples are
	 * complete even when OpenVPN pushes the information in a
	 * wrong order (unlikely).
	 */

	route = ov_route_lookup(key, "route_network_", routes);
	if (route != NULL) {
		route->host = g_strdup(value);
		return;
	}

	route = ov_route_lookup(key, "route_netmask_", routes);
	if (route != NULL) {
		route->netmask = g_strdup(value);
		return;
	}

	route = ov_route_lookup(key, "route_gateway_", routes);
	if (route != NULL)
		route->gateway = g_strdup(value);
}

static int mask2prefix(const char *str)
{
	in_addr_t mask = inet_network(str);
	int prefix;

	for (prefix = 0; mask != 0; prefix++)
		mask <<= 1;
	return prefix;
}

struct dns_state {
	char **servers;
	int n_servers;
	char **domains;
	int n_domains;
};

static void parse_dns_cleanup(struct dns_state *dns)
{
	g_strfreev(dns->domains);
	g_strfreev(dns->servers);
	g_free(dns);
}

static struct dns_state *parse_dns_init(void)
{
	struct dns_state *dns = g_try_new0(struct dns_state, 1);
	if (dns == NULL) {
		connman_error("%s: no memory for dns state", __func__);
		return NULL;
	}
	dns->servers = g_try_new0(char *, MAXDNS + 1);
	if (dns->servers == NULL) {
		connman_error("%s: no memory for dns_servers", __func__);
		goto bad;
	}
	dns->domains = g_try_new0(char *, MAXDNS + 1);
	if (dns->domains == NULL) {
		connman_error("%s: no memory for dns_domains", __func__);
		goto bad;
	}
	return dns;
bad:
	parse_dns_cleanup(dns);
	return NULL;
}

static void parse_dhcp_option(struct dns_state *dns, gchar **opts)
{
#define	iseq(_a, _b) (g_ascii_strcasecmp((_a), (_b)) == 0)
	if (opts[1] == NULL)
		return;
	if (iseq(opts[1], "DOMAIN") && opts[2] != NULL) {
		if (dns->n_domains >= MAXDNS) {
			connman_warn("%s: too many DNS search domains, "
			    "%s ignored", __func__, opts[2]);
			return;
		}
		dns->domains[dns->n_domains++] = g_strdup(opts[2]);
	} else if (iseq(opts[1], "DNS") && opts[2] != NULL) {
		if (dns->n_servers >= MAXDNS) {
			connman_warn("%s: too many DNS servers, %s ignored",
			    __func__, opts[2]);
			return;
		}
		dns->servers[dns->n_servers++] = g_strdup(opts[2]);
	}
#undef iseq
}

static void set_string(char **storage, const char *str)
{
	g_free(*storage);
	*storage = g_strdup(str);
}

struct foreign_option {
	const char *key;
	const char *value;
};

static gint foreign_option_compare(gconstpointer a, gconstpointer b,
    gpointer user_data) {
	const struct foreign_option *option_a = (const void *) a;
	const struct foreign_option *option_b = (const void *) b;
	return g_strcmp0(option_a->key, option_b->key);
}

static void parse_foreign_option(gpointer opt, gpointer dns) {
	const struct foreign_option *option = (const void *) opt;
	gchar **options = g_strsplit(option->value, " ", 3);

	if (options[0] != NULL && g_ascii_strcasecmp(options[0], "dhcp-option") == 0)
	  parse_dhcp_option(dns, options);
	g_strfreev(options);
}

static int ov_notify(DBusMessage *msg, struct connman_provider *provider)
{
#define	iseq(_a, _b) (g_ascii_strcasecmp((_a), (_b)) == 0)
	struct openvpn_data *data = vpn_get_specific_data(provider);
	DBusMessageIter iter, dict;
	const char *reason, *key;
	char *value;
	GSequence *foreign_options;
	GHashTable *routes;
	struct dns_state *dns;
	const char *trusted_ip = NULL;

	dbus_message_iter_init(msg, &iter);

	dbus_message_iter_get_basic(&iter, &reason);
	dbus_message_iter_next(&iter);

	dbus_message_iter_init(msg, &iter);

	dbus_message_iter_get_basic(&iter, &reason);
	dbus_message_iter_next(&iter);

	_DBG_VPN("reason %s provider %p", reason, provider);

	if (provider == NULL) {
		connman_error("%s: no provider found", __func__);
		return VPN_STATE_FAILURE;
	}

	if (strcmp(reason, "up") != 0)
		return VPN_STATE_DISCONNECT;

	dns = parse_dns_init();
	if (dns == NULL) {
		connman_error("%s: no memory for dns state", __func__);
		return VPN_STATE_FAILURE;
	}

	foreign_options = g_sequence_new(NULL);

	routes = g_hash_table_new_full(g_direct_hash, g_direct_equal,
					NULL, destroy_route);

	dbus_message_iter_recurse(&iter, &dict);
	while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) {
		DBusMessageIter entry;

		dbus_message_iter_recurse(&dict, &entry);
		dbus_message_iter_get_basic(&entry, &key);
		dbus_message_iter_next(&entry);
		dbus_message_iter_get_basic(&entry, &value);

		_DBG_VPN("%s = %s", key, value);

		if (iseq(key, "ifconfig_local")) {
			set_string(&data->ipaddr.local, value);
			data->ipaddr.mask |= CONNMAN_IPCONFIG_LOCAL;
		} else if (iseq(key, "ifconfig_broadcast")) {
			set_string(&data->ipaddr.broadcast, value);
			data->ipaddr.mask |= CONNMAN_IPCONFIG_BCAST;
		} else if (iseq(key, "ifconfig_netmask")) {
			data->ipaddr.prefixlen = mask2prefix(value);
			data->ipaddr.mask |= CONNMAN_IPCONFIG_PREFIX;
		} else if (iseq(key, "ifconfig_remote")) {
			set_string(&data->ipaddr.peer, value);
			data->ipaddr.mask |= CONNMAN_IPCONFIG_PEER;
		} else if (iseq(key, "route_vpn_gateway")) {
			set_string(&data->ipaddr.gateway, value);
			data->ipaddr.mask |= CONNMAN_IPCONFIG_GW;
		} else if (iseq(key, "trusted_ip")) {
			trusted_ip = value;
		} else if (iseq(key, "tun_mtu")) {
			int mtu = (int) strtol(value, NULL, 10);
			/* NB: restrict min MTU per dhcpcd-script */
			if (576 <= mtu) {
				data->ipaddr.mtu = mtu;
				data->ipaddr.mask |= CONNMAN_IPCONFIG_MTU;
			} else
				connman_warn("%s: MTU %s too small (ignored)",
				    __func__, value);
		} else if (g_str_has_prefix(key, "foreign_option_") == TRUE) {
			struct foreign_option *option =
				g_new0(struct foreign_option, 1);
			option->key = key;
			option->value = value;
			g_sequence_insert_sorted(foreign_options,
						 option,
						 foreign_option_compare,
						 NULL);
		} else if (g_str_has_prefix(key, "route_") == TRUE) {
			ov_append_route(key, value, routes);
		}

		dbus_message_iter_next(&dict);
	}

	if ((data->ipaddr.mask & CONNMAN_IPCONFIG_GW) && trusted_ip != NULL) {
		/*
		 * If we are creating a new default gateway, we need
		 * to first pin a host route to the VPN server so that
		 * encapsulated packets continue to be sent to the VPN
		 * server endpoint.
		 *
		 * NB: route_net_gateway may have the gateway but we also
		 *     need the interface index and both are returned by
		 *     calling connman_inet_get_route so don't bother.
		 */
		_DBG_VPN("add host VPN server route for %s", trusted_ip);
		data->vpn_addr = inet_addr(trusted_ip);
		if (!connman_inet_get_route(data->vpn_addr,
					    &data->gw_index,
					    &data->gw_addr,
					    NULL)) {
			connman_error("%s: unable to get route to %s",
			    __func__, trusted_ip);
			return VPN_STATE_FAILURE;
		}
		connman_inet_add_hostroute(data->gw_index, data->vpn_addr,
		    data->gw_addr);
	}

	g_sequence_foreach(foreign_options, parse_foreign_option, dns);
	if (dns->n_servers > 0) {
		data->ipaddr.dns_servers = dns->servers;
		data->ipaddr.mask |= CONNMAN_IPCONFIG_DNS;
	}
	if (dns->n_domains > 0) {
		data->ipaddr.search_domains = dns->domains;
		data->ipaddr.mask |= CONNMAN_IPCONFIG_SEARCH;
	}
	connman_provider_ipconfig_set(provider, &data->ipaddr);

	parse_dns_cleanup(dns);
	/* NB: dns configuration is sent for restarts */
	data->ipaddr.mask &= ~(CONNMAN_IPCONFIG_DNS|CONNMAN_IPCONFIG_SEARCH);

	g_sequence_free(foreign_options);

	g_hash_table_foreach(routes, ov_provider_append_routes, provider);
	/* TODO(sleffler): routes are not sent for restarts */
	g_hash_table_destroy(routes);

	return VPN_STATE_CONNECT;
#undef iseq
}

/*
 * OpenVPN management channel support.
 */

static const char *kOTPProperty = "OpenVPN.OTP";
static const char *kPasswordProperty = "OpenVPN.Password";
static const char *kPINProperty = "OpenVPN.Pkcs11.PIN";
static const char *kUserProperty = "OpenVPN.User";

static void mgmt_event_need_auth(struct mgmt_server_data *data,
    const char *tag);
static void mgmt_event_need_tpm_pin(struct mgmt_server_data *data,
    const char *tag);
static void mgmt_event_need_static_challenge(struct mgmt_server_data *data,
    const char *tag, char *cr);
static void mgmt_event_need_dynamic_challenge(struct mgmt_server_data *data,
    const char *tag);
static void mgmt_close_channel(struct mgmt_server_data *data);

static ssize_t mgmt_send(struct mgmt_server_data *data, const char *s,
    ssize_t len, const char *func)
{
	int fd = g_io_channel_unix_get_fd(data->channel);
	ssize_t n = write(fd, s, len);
	if (n < 0)
		connman_error("%s: write error: %s", func, strerror(errno));
	else if (n != len)
		connman_error("%s: write short, %zd != %zd", func, n, len);
	return n;
}

static void mgmt_send_username(struct mgmt_server_data *data,
    const char *tag, const char *username, const char *func)
{
	gchar *str = g_strdup_printf("username \"%s\" %s\n", tag, username);

	_DBG_VPN("tag %s username %s", tag,
	    connman_log_get_masked_value(kUserProperty, username));

	mgmt_send(data, str, strlen(str), func);
	g_free(str);
}

static void mgmt_send_password(struct mgmt_server_data *data,
    const char *tag, const char *password, const char *func)
{
	gchar *str = g_strdup_printf("password \"%s\" \"%s\"\n",
	    tag, password);

	_DBG_VPN("tag %s password %s", tag,
	    connman_log_get_masked_value(kPasswordProperty, password));

	mgmt_send(data, str, strlen(str), func);
	g_free(str);
}

/*
 * Send a static challenge/response msg on receiving a
 * respose; see the protocol description below.
 */
static void mgmt_send_static_challenge_response(struct mgmt_server_data *data,
    const char *tag, const char *username, const char *password,
    const char *otp)
{
	gchar *reply;

	_DBG_VPN("tag %s username %s password %s otp %s", tag,
	    connman_log_get_masked_value(kUserProperty, username),
	    connman_log_get_masked_value(kPasswordProperty, password),
	    connman_log_get_masked_value(kOTPProperty, otp));

	mgmt_send_username(data, tag, username, __func__);

	reply = g_strdup_printf("SCRV1:%s:%s", password, otp);
	mgmt_send_password(data, tag, reply, __func__);
	g_free(reply);
}

/*
 * Send a dynamic challenge/response msg on receiving a
 * response; see the protocol description below.
 */
static void mgmt_send_dynamic_challenge_response(struct mgmt_server_data *data,
    const char *tag, const char *username, const char *state_id,
    const char *otp)
{
	gchar *reply;

	_DBG_VPN("username %s state_id %s otp %s",
	    connman_log_get_masked_value(kUserProperty, username),
	    state_id,
	    connman_log_get_masked_value(kOTPProperty, otp));

	mgmt_send_username(data, tag, username, __func__);

	reply = g_strdup_printf("CRV1::%s:%s", state_id, otp);
	mgmt_send_password(data, tag, reply, __func__);
	g_free(reply);
}

/*
 * >STATE:* msg support.
 *
 * State messages are of the form:
 *    >STATE:<date>,<state>,<detail>,<local-ip>,<remote-ip>
 * where:
 * <date> is the current time (since epoch) in seconds
 * <state> is one of:
 *    INITIAL, CONNECTING, WAIT, AUTH, GET_CONFIG, ASSIGN_IP,
 *    ADD_ROUTES, CONNECTED, RECONNECTING, EXITING, RESOLVE, TCP_CONNECT
 * <detail> is a free-form string giving details about the state change
 * <local-ip> is a dotted-quad for the local IPv4 address (when available)
 * <remote-ip> is a dotted-quad for the remote IPv4 address (when available)
 */
static void mgmt_event_state(struct mgmt_server_data *data, const char *tag)
{
	const char *cp = strchr(tag, ',');

	if (cp == NULL) {
		connman_error("%s: bad >STATE msg \"%s\"", __func__, tag);
		return;
	}
	cp++;		/* skip ',' */
	if (g_str_has_prefix(cp, "CONNECTING,") == TRUE) {
		data->state = OV_STATE_CONNECTING;
	} else if (g_str_has_prefix(cp, "WAIT,") == TRUE) {
		data->state = OV_STATE_WAIT;
	} else if (g_str_has_prefix(cp, "AUTH,") == TRUE) {
		data->state = OV_STATE_AUTH;
	} else if (g_str_has_prefix(cp, "GET_CONFIG,") == TRUE) {
		data->state = OV_STATE_GET_CONFIG;
	} else if (g_str_has_prefix(cp, "ASSIGN_IP,") == TRUE) {
		data->state = OV_STATE_ASSIGN_IP;
	} else if (g_str_has_prefix(cp, "ADD_ROUTES,") == TRUE) {
		data->state = OV_STATE_ADD_ROUTES;
	} else if (g_str_has_prefix(cp, "CONNECTED,") == TRUE) {
		data->state = OV_STATE_CONNECTED;
	} else if (g_str_has_prefix(cp, "RECONNECTING,") == TRUE) {
		data->state = OV_STATE_RECONNECTING;
		vpn_reconnect(data->provider);
	} else if (g_str_has_prefix(cp, "RESOLVE,") == TRUE) {
		data->state = OV_STATE_RESOLVE;
	} else if (g_str_has_prefix(cp, "EXITING,") == TRUE) {
		data->state = OV_STATE_EXITING;
	} else {
		connman_warn("%s: unknown STATE, openvpn sent \"%s\"",
		    __func__, tag);
		return;
	}

	/* TODO(sleffler) dispatch signal for UI? */
}

static void mgmt_send_state(struct mgmt_server_data *data,
    const char *tag, const char *func)
{
	gchar *str = g_strdup_printf("state %s\n", tag);

	_DBG_VPN("state %s", tag);

	mgmt_send(data, str, strlen(str), func);
	g_free(str);
}

/*
 * Authentication callback support.  When an Auth request is
 * received over the management channel we post a request to
 * an associated Agent for the necessary data.  On receiving
 * a reply we get a callback with the state block we construct
 * to hold the data required to complete the request.  If there
 * is no reply or an error occurs we also get a callback but
 * a "failure" indication.  If we hit an Agent failure we shut
 * down the management channel which will cause openvpn to abort
 * and unwind our side to force the provider and service into
 * an failure state.
 */
struct need_auth_cb_data {
	struct mgmt_server_data *data;
	char *tag;
};
static void *new_auth_cb_arg(struct mgmt_server_data *data, const char *tag)
{
	struct need_auth_cb_data *arg = g_try_new0(struct need_auth_cb_data, 1);
	if (arg != NULL) {
		arg->data = data;
		arg->tag = g_strdup(tag);
	}
	return arg;
}
static void free_auth_cb_arg(struct need_auth_cb_data *arg)
{
	g_free(arg->tag);
	g_free(arg);
}

/*
 * Agent callback for user/password and dynamic challenge/response
 * authentication requests (static c/r handled separately).
 */
static void need_auth_cb(struct connman_provider *provider, void *arg,
    int success)
{
	struct need_auth_cb_data *data = arg;

	if (success)
		mgmt_event_need_auth(data->data, data->tag);
	else
		mgmt_close_channel(data->data);
	free_auth_cb_arg(data);
}

/*
 * >PASSWORD:Need "Auth" support for user/password requests.
 * If we have all the data just send a response.  Otherwise
 * notify the Agent and wait for a reply.
 */
static void mgmt_event_need_auth(struct mgmt_server_data *data,
    const char *tag)
{
	struct connman_provider *provider = data->provider;
	const char *user = connman_provider_get_string(provider, kUserProperty);
	const char *passwd = connman_provider_get_string(provider,
	    kPasswordProperty);

	if (user == NULL || passwd == NULL) {
		struct need_auth_cb_data *arg = new_auth_cb_arg(data, tag);

		_DBG_VPN("no username/password yet; defer reply");
		if (connman_provider_request_input(provider,
		    need_auth_cb, new_auth_cb_arg(data, tag),
		    "User", kUserProperty,
		    "Password", kPasswordProperty, NULL) < 0)
			need_auth_cb(provider, arg, FALSE);
		return;
	}
	mgmt_send_username(data, tag, user, __func__);
	mgmt_send_password(data, tag, passwd, __func__);
}

/*
 * Agent callback for TPM PIN requests.
 */
static void need_tpm_pin_cb(struct connman_provider *provider, void *arg,
    int success)
{
	struct need_auth_cb_data *data = arg;

	if (success)
		mgmt_event_need_tpm_pin(data->data, data->tag);
	else
		mgmt_close_channel(data->data);
	free_auth_cb_arg(data);
}

/*
 * >PASSWORD:Need TPM PIN support.
 */
static void mgmt_event_need_tpm_pin(struct mgmt_server_data *data,
    const char *tag)
{
	struct connman_provider *provider = data->provider;
	const char *pin = connman_provider_get_string(provider, kPINProperty);

	if (pin == NULL) {
		struct need_auth_cb_data *arg = new_auth_cb_arg(data, tag);

		if (connman_provider_request_input(provider,
		    need_tpm_pin_cb, new_auth_cb_arg(data, tag),
		    "PIN", kPINProperty, NULL) < 0)
			need_tpm_pin_cb(provider, arg, FALSE);
		_DBG_VPN("no PIN; defer reply");
	} else
		mgmt_send_password(data, tag, pin, __func__);
}

/*
 * Agent callback for static challenge/response requests.
 */
static void need_static_challenge_cb(struct connman_provider *provider,
    void *arg, int success)
{
	struct need_auth_cb_data *data = arg;

	if (success)
		mgmt_event_need_static_challenge(data->data, data->tag, NULL);
	else
		mgmt_close_channel(data->data);
	free_auth_cb_arg(data);
}

/*
 * >PASSWORD:Need "Auth" support for static challenge/response requests.
 */
static void mgmt_event_need_static_challenge(struct mgmt_server_data *data,
    const char *tag, char *cr)
{
	struct connman_provider *provider = data->provider;
	const char *username = connman_provider_get_string(provider,
	    kUserProperty);
	const char *password = connman_provider_get_string(provider,
	    kPasswordProperty);
	const char *otp = connman_provider_get_string(provider, kOTPProperty);

	if (username == NULL || password == NULL || otp == NULL) {
		struct need_auth_cb_data *arg = new_auth_cb_arg(data, tag);

		/* missing required credentials, request input */
		_DBG_VPN("missing username/passwd/OTP; defer reply");
		if (connman_provider_request_input(provider,
		    need_static_challenge_cb, arg,
		    "User", kUserProperty,
		    "Password", kPasswordProperty,
		    "OTP", kOTPProperty, NULL) < 0)
			need_static_challenge_cb(provider, arg, FALSE);
	} else {
		gchar *password_base64 = g_base64_encode(
		    (const guchar *)password, strlen(password));
		gchar *otp_base64 = g_base64_encode(
		    (const guchar *)otp, strlen(otp));

		mgmt_send_static_challenge_response(data, tag, username,
		    password_base64, otp_base64);

		g_free(otp_base64);
		g_free(password_base64);

		/* NB: never re-use OTP, also avoids looping */
		connman_provider_clear_property(provider, kOTPProperty);
	}
}

/*
 * Agent callback for dynamic challenge/response requests.
 */
static void need_dynamic_challenge_cb(struct connman_provider *provider,
    void *arg, int success)
{
	struct need_auth_cb_data *data = arg;

	if (success)
		mgmt_event_need_dynamic_challenge(data->data, data->tag);
	else
		mgmt_close_channel(data->data);
	free_auth_cb_arg(data);
}

/*
 * >PASSWORD:Need "Auth" support for dynamic challenge/response
 * requests.  If we have all the data just send a response.
 * Otherwise notify the Agent and wait for a reply.
 */
static void mgmt_event_need_dynamic_challenge(struct mgmt_server_data *data,
    const char *tag)
{
	struct connman_provider *provider = data->provider;
	const char *otp = connman_provider_get_string(provider, kOTPProperty);

	if (otp == NULL) {
		struct need_auth_cb_data *arg = new_auth_cb_arg(data, tag);

		/* missing OTP, request input */
		_DBG_VPN("missing OTP; defer reply");
		if (connman_provider_request_input(provider,
		    need_dynamic_challenge_cb, new_auth_cb_arg(data, tag),
		    "OTP", kOTPProperty, NULL) < 0)
			need_dynamic_challenge_cb(provider, arg, FALSE);
	} else {
		mgmt_send_dynamic_challenge_response(data, tag,
		    data->username, data->state_id, otp);

		g_free(data->username);
		data->username = NULL;

		g_free(data->state_id);
		data->state_id = NULL;

		/* NB: never re-use OTP, also avoids looping */
		connman_provider_clear_property(provider, kOTPProperty);
	}
}

/*
 * Parse Dynamic Challenge/Response Protocol.  The failure
 * msg contains the challenge question formatted according to:
 *
 * CRV1:<flags>:<state_id>:<username_base64>:<challenge_text>
 *
 * flags: a series of optional, comma-separate flags:
 *  E : echo the response when the user types it
 *  R : a response is required
 *
 * state_id: an opaque string that should be returned to the
 *  server along with the response.
 *
 * username_base64 : the username formatted as base64
 *
 * challenge_text : the challenge text to be shown to the user
 *
 * After prompting for the reponse we must respond to the next
 * >PASSWORD:Need request with:
 *
 * Username: [username decoded from username_base64]
 * Password: CRV1::<state_id>::<response_text>
 *
 * (using the values sent in the failure message).
 */
static void missing_colon(const char *func, const char *what, const char *data)
{
	connman_error("%s: no ':' parsing %s in \"%s\"", func, what, data);
}
static void mgmt_event_challenge_response(struct mgmt_server_data *data,
    char *emsg)
{
	char *cp, *state_id, *username_base64, *challenge_text;
	int respond, echo;

	respond = echo = FALSE;
	/* NB: caller verifies "CRV1:" is present so +5 is ok here */
	for (cp = emsg + 5; *cp != ':'; cp++) {
		if (*cp == '\0') {
			missing_colon(__func__, "flags", emsg);
			return;
		}
		if (*cp == 'R')
			respond = TRUE;
		else if (*cp == 'E')
			echo = TRUE;
	}
	state_id = cp+1;
	cp = strchr(state_id, ':');
	if (cp == NULL) {
		missing_colon(__func__, "state_id", emsg);
		return;
	}
	*cp = '\0';
	username_base64 = cp+1;
	cp = strchr(username_base64, ':');
	if (cp == NULL) {
		missing_colon(__func__, "username", emsg);
		return;
	}
	*cp = '\0';
	challenge_text = cp+1;

	connman_info("%s: state_id \"%s\" username \"%s\" challenge \"%s\"%s%s",
	   __func__, state_id, username_base64, challenge_text,
	   respond ? ", Respond" : "", echo ? ", Echo" : "");

	if (respond) {
		gsize len;

		/* TODO(sleffler) state_id and username should be NULL, should we assert? */
		g_free(data->state_id);
		data->state_id = g_strdup(state_id);

		g_free(data->username);
		/* TODO(sleffler) can &len be NULL? */
		data->username = (char *)g_base64_decode(username_base64, &len);

		mgmt_event_need_dynamic_challenge(data, "Auth");
	}
}

/*
 * Handle a password verification failure.  We process
 * challenge/response requests and push everything else
 * back to the client.
 */
static void missing_token(const char *func, const char *what, const char *data)
{
	connman_error("%s: missing '%s' in \"%s\"", func, what, data);
}
static void mgmt_event_verification(struct mgmt_server_data *data,
    char *line)
{
	char *emsg, *cp;

	if (g_str_has_prefix(line, ">PASSWORD:Verification Failed: 'Auth'") == FALSE)
		return;

	/*
	 * Auth failures may include a custom server-generated string.
	 * Dynamic c/r msgs are of the form ['CRV1:...']; we extract
	 * those here and hand them off for processing.  All other msgs
	 * are sent back to the client for presentation.
	 */
	emsg = strchr(line, '[');
	if (emsg != NULL) {
		emsg++;
		if (emsg[0] == '\'')
			emsg++;
		cp = strchr(emsg, ']');
		if (cp == NULL) {
			missing_token(__func__, "]", line);
			goto done;
		}
		if (cp[-1] == '\'')
			cp--;
		*cp = '\0';

		if (g_str_has_prefix(emsg, "CRV1:") == TRUE) {
			mgmt_event_challenge_response(data, emsg);
			return;
		}
		/* NB: stripped ['...'] from the original string */
	}
done:
	_DBG_VPN("Auth failure '%s'", emsg);
	/* NB: no way to pass auth failure indication to client */
	connman_provider_indicate_error(data->provider,
	    CONNMAN_PROVIDER_ERROR_BAD_PASSPHRASE);

	/* TODO(sleffler) clear state, OpenVPN.Password? */
}

/*
 * Process data from the management control socket connected to openvpn.
 * We process only ">PASSWORD" requests; tossing everything else.
 */
static gboolean mgmt_server_event(GIOChannel *channel, GIOCondition condition,
				  gpointer user_data)
{
	struct mgmt_server_data *data = user_data;
	char buf[512];
	int s, i;
	ssize_t len;
	gchar **lines;

	if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
		connman_error("%s: server channel error (condition 0x%x)",
		    __func__, condition);
		data->watch = 0;
		return FALSE;
	}

	s = g_io_channel_unix_get_fd(channel);

	len = read(s, buf, sizeof(buf)-1);
	if (len < 0) {
		/* TODO(sleffler) disable or recover? */
		connman_error("%s: read error %s", __func__, strerror(errno));
		data->watch = 0;
		return FALSE;
	}
	/* TODO(sleffler) buffer data until we see a \n? */

	buf[len] = '\0';

	lines = g_strsplit(buf, "\n", 0);
	for (i = 0; lines[i] != NULL; i++) {
		if (lines[i][0] == '\0')
			continue;
		_DBG_VPN("\"%s\"", lines[i]);
		if (g_str_has_prefix(lines[i], ">INFO") == TRUE)
			continue;
		if (g_str_has_prefix(lines[i], ">PASSWORD:Need ") == TRUE) {
			char *type, *etype;

			/* extract tag, e.g. "Auth" */
			type = strchr(lines[i], '\'');
			if (type == NULL) {
				missing_token(__func__, "open-'", lines[i]);
				goto skip;
			}
			type++;
			etype = strchr(type, '\'');
			if (etype == NULL) {
				missing_token(__func__, "close-'", lines[i]);
				goto skip;
			}
			*etype++ = '\0';

			if (g_strcmp0(type, "Auth") == 0) {
				gchar *sc;

				/* check for static-challenge component */
				sc = g_strstr_len(etype, strlen(etype), "SC:");
				if (sc != NULL) {
					mgmt_event_need_static_challenge(data,
					    type, sc);
				} else
					mgmt_event_need_auth(data, type);
				continue;
			}

			if (g_str_has_prefix(type, "User-Specific TPM Token") == TRUE) {
				mgmt_event_need_tpm_pin(data, type);
				continue;
			}
		}
		if (g_str_has_prefix(lines[i], ">PASSWORD:Verif") == TRUE) {
			mgmt_event_verification(data, lines[i]);
			continue;
		}
		if (g_str_has_prefix(lines[i], ">STATE:") == TRUE) {
			mgmt_event_state(data, lines[i]);
			continue;
		}
	skip:
		connman_info("%s: ignore \"%s\"", __func__, lines[i]);
	}
	g_strfreev(lines);

	return TRUE;
}

/*
 * Management channel callback to accept a connection from openvpn.
 * This callback is setup for the socket we listen on.  Once we accept
 * a connection on it we then discard the listen socket and arrange
 * for callbacks over the data socket by mgmt_server_event.
 */
static gboolean mgmt_server_accept(GIOChannel *channel, GIOCondition condition,
				  gpointer user_data)
{
	struct mgmt_server_data *data = user_data;
	int s;

	if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
		connman_error("%s: server channel error (condition 0x%x)",
		    __func__, condition);
		data->watch = 0;
		return FALSE;
	}

	/* TODO(sleffler): verify peer's address is INADDR_LOOPBACK */
	s = accept(g_io_channel_unix_get_fd(channel), NULL, NULL);
	if (s < 0) {
		connman_error("%s: accept error %s", __func__, strerror(errno));
		data->watch = 0;
		return FALSE;
	}

	/* clear old state to prepare for replacements */
	g_source_remove(data->watch);
	data->watch = 0;

	g_io_channel_unref(data->channel);

	/* now setup channel for accepted socket */
	data->channel = g_io_channel_unix_new(s);
	if (data->channel == NULL) {
		connman_error("Failed to create data channel for %s",
		    data->vpnhost);
		close(s);
		/* NB: data->watch already zero */
		return FALSE;
	}
	g_io_channel_set_close_on_unref(data->channel, TRUE);

	data->watch = g_io_add_watch(data->channel, G_IO_IN,
				     mgmt_server_event, data);

	connman_info("OpenVPN management channel for %s created @%s:%d",
	    data->vpnhost, inet_ntoa(data->addr.sin_addr),
	    ntohs(data->addr.sin_port));

	mgmt_send_state(data, "on", __func__);

	return TRUE;
}

static struct mgmt_server_data *mgmt_create_server(
    struct connman_provider *provider, const char *vpnhost)
{
	struct mgmt_server_data *data;
	int s;
	socklen_t slen;

	_DBG_VPN("vpnhost %s", vpnhost);

	data = g_try_new0(struct mgmt_server_data, 1);
	if (data == NULL) {
		connman_error("Failed to allocate server data");
		return NULL;
	}

	s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s < 0) {
		connman_error("Server socket create failed for %s (%s)",
		    vpnhost, strerror(errno));
		g_free(data);
		return NULL;
	}

	memset(&data->addr, 0, sizeof(data->addr));
	data->addr.sin_family = AF_INET;
	data->addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	if (bind(s, (struct sockaddr *) &data->addr, sizeof(data->addr)) < 0) {
		connman_error("Could not bind address for %s: %s",
		    vpnhost, strerror(errno));
		goto bad;
	}

	if (listen(s, 1) < 0) {
		connman_error("Server socket listen failed for %s: %s",
		    vpnhost, strerror(errno));
		goto bad;
	}

	slen = sizeof(data->addr);
	if (getsockname(s, (struct sockaddr *) &data->addr, &slen) < 0) {
		connman_error("Could not get bound address for %s: %s",
		    vpnhost, strerror(errno));
		goto bad;
	}

	data->channel = g_io_channel_unix_new(s);
	if (data->channel == NULL) {
		connman_error("Failed to create server channel for %s",
		    vpnhost);
		goto bad;
	}
	g_io_channel_set_close_on_unref(data->channel, TRUE);

	data->watch = g_io_add_watch(data->channel, G_IO_IN,
				     mgmt_server_accept, data);

	data->provider = connman_provider_ref(provider);
	data->vpnhost = g_strdup(vpnhost);
	data->state = OV_STATE_UNKNOWN;

	return data;
bad:
	close(s);
	g_free(data);
	return NULL;
}

static void mgmt_close_channel(struct mgmt_server_data *data)
{
	if (data->watch > 0) {
		g_source_remove(data->watch);
		data->watch = -1;
	}
	if (data->channel != NULL) {
		g_io_channel_unref(data->channel);
		data->channel = NULL;
	}
}

static void mgmt_destroy_server(struct mgmt_server_data *data)
{
	_DBG_VPN("vpnhost %s", data->vpnhost);

	mgmt_close_channel(data);

	connman_provider_unref(data->provider);
	g_free(data->username);
	g_free(data->password);
	g_free(data->state_id);
	g_free(data->vpnhost);
	g_free(data);
}

static int ov_init_data(struct connman_provider *provider, const char *vpnhost)
{
	struct openvpn_data *data;

	data = g_try_new0(struct openvpn_data, 1);
	if (data == NULL) {
		connman_error("%s: out of memory", __func__);
		return -ENOMEM;
	}
	data->gw_index = (uint32_t)-1;
	data->ipaddr.af = AF_INET;
	data->ipaddr.mask |= CONNMAN_IPCONFIG_AF;
	vpn_set_specific_data(provider, data);
	return 0;
}

static void ov_cleanup_data(struct connman_provider *provider)
{
	struct openvpn_data *data = vpn_get_specific_data(provider);

	_DBG_VPN("provider %p data %p", provider, data);

	if (data == NULL)
		return;
	if (data->server != NULL)
		mgmt_destroy_server(data->server);
	if (data->gw_index != (uint32_t)-1) {
		_DBG_VPN("tear down VPN host route");
		connman_inet_del_hostroute(data->gw_index, data->vpn_addr,
		    data->gw_addr);
	}
	if (data->tls_auth_file != NULL) {
		unlink(data->tls_auth_file);
		g_free(data->tls_auth_file);
	}
	if (data->ipaddr.mask & CONNMAN_IPCONFIG_LOCAL)
		g_free(data->ipaddr.local);
	if (data->ipaddr.mask & CONNMAN_IPCONFIG_BCAST)
		g_free(data->ipaddr.broadcast);
	if (data->ipaddr.mask & CONNMAN_IPCONFIG_PEER)
		g_free(data->ipaddr.peer);
	if (data->ipaddr.mask & CONNMAN_IPCONFIG_GW)
		g_free(data->ipaddr.gateway);
	vpn_set_specific_data(provider, NULL);
	g_free(data);
}

static void ov_vpn_died(struct connman_task *task, void *user_data)
{
	struct connman_provider *provider = user_data;

	_DBG_VPN("provider %p", provider);

	ov_cleanup_data(provider);
	vpn_died(task, user_data, CONNMAN_PROVIDER_ERROR_CONNECT_FAILED);
}

#define	OPT_STR(property, option) do {					 \
	const char *s = connman_provider_get_string(provider, property); \
	if (s != NULL)							 \
		connman_task_add_argument(task, option, "%s", s);	 \
} while (0)
#define	OPT_BOOL(property, option) do {					 \
	const char *s = connman_provider_get_string(provider, property); \
	if (s != NULL)							 \
		connman_task_add_argument(task, option, NULL);		 \
} while (0)

int add_mgmt_arguments(struct connman_provider *provider,
		       struct connman_task *task,
		       const char *vpnhost)
{
	struct openvpn_data *data = vpn_get_specific_data(provider);
	const char *static_challenge;
	gchar *port;

	/*
	 * Setup a management control channel between openvpn and us
	 * to do things like query for passwords.  We specify that
	 * openvpn connects back to us to avoid a race on startup and
	 * to allow us to bind a (random) system-selected port (on
	 * the loopback interface) to use.  This plus accepting only
	 * one connection minimizes the window by which a process can
	 * setup this channel over which sensitive data are passed.
	 */
	data->server = mgmt_create_server(provider, vpnhost);
	if (data->server == NULL) {
		/* NB: mgmt_create_server logs reason */
		ov_cleanup_data(provider);
		return -EINVAL;
	}

	/* TODO(sleffler) total hack for crappy api */
	connman_task_add_argument(task,	"--management",
	    inet_ntoa(data->server->addr.sin_addr));
	port = g_strdup_printf("%d", ntohs(data->server->addr.sin_port));
	connman_task_add_argument(task, port, NULL);
	g_free(port);

	connman_task_add_argument(task,	"--management-client", NULL);
	connman_task_add_argument(task,	"--management-query-passwords", NULL);

	OPT_BOOL("OpenVPN.AuthUserPass","--auth-user-pass");

	static_challenge = connman_provider_get_string(provider,
	    "OpenVPN.StaticChallenge");
	if (static_challenge != NULL) {
		connman_task_add_argument(task,	"--static-challenge",
		    static_challenge);
		/* NB: force echo */
		connman_task_add_argument(task, "1", NULL);
	}

	/* TODO(sleffler) verify OpenVPN.Pkcs11.PIN is present */
	/* TODO(sleffler) set/verify OpenVPN.AuthRetry? */
	return 0;
}

/*
 * Duplicate a string from /etc/lsb-release.  The data are
 * scrubbed for whitespace and any terminating \n is removed.
 */
static char *dup_value(char *str)
{
	char buf[256], c;
	int i, j;

	j = 0;
	for (i = 0; (c = str[i]) != '\0'; i++) {
		if (c == ' ')		/* remove whitespace */
			continue;
		if (c == '\n')		/* stop at first \n */
			break;
		buf[j++] = c;
	}
	buf[j] = '\0';
	return strdup(buf);
}

/*
 * Read platform name and version from /etc/lsb-release
 * and install them in platform_name and platform_version,
 * respectively.  Return true on success.
 */
static gboolean read_platform_data(void)
{
	char line[256], name[256], version[256];
	FILE *fd;

	fd = fopen(kLSBReleaseFilename, "r");
	if (fd == NULL) {
		connman_error("%s: Cannot open %s", __func__,
		    kLSBReleaseFilename);
		return FALSE;
	}
	name[0] = version[0] = '\0';
	while (fgets(line, sizeof(line), fd) != NULL) {
		char *cp = strchr(line, '=');
		if (cp == NULL)
			continue;
		*cp++ = '\0';
		if (strcmp(line, kChromeOSReleaseName)== 0)
			strncpy(name, cp, sizeof(name));
		else if (strcmp(line, kChromeOSReleaseVersion) == 0)
			strncpy(version, cp, sizeof(version));
	}
	fclose(fd);
	if (name[0] == '\0' || version[0] == '\0') {
		connman_error("%s: Missing data: name=\"%s\" version=\"%s\"",
		    __func__, name, version);
		return FALSE;
	}

	/* TODO(sleffler) worth checking return values? */
	platform_name = dup_value(name);
	platform_version = dup_value(version);
	return (platform_name != NULL && platform_version != NULL);
}

/*
 * Add the platform name and version information to the environment
 * so that openvpn will send them to the server (when
 * OpenVPN.PushPeerInfo is set).
 */
static void add_platform_vars(struct connman_task *task)
{
	if (platform_name == NULL && !read_platform_data())
		return;

	/* NB: these are passed through the environment */
	connman_task_add_variable(task, "IV_PLAT", platform_name);
	connman_task_add_variable(task, "IV_PLAT_REL", platform_version);
}

static int ov_connect(struct connman_provider *provider,
		struct connman_task *task, const char *if_name)
{
	const char *vpnhost;
	const char *remote_cert_tls;
	const char *tls_auth_contents;
	const char *ca_cert_nss;
	const char *pkcs11_id;
	const char *verb;
	int err, fd;

	vpnhost = connman_provider_get_string(provider, CONNMAN_PROVIDER_HOST);
	if (!vpnhost) {
		connman_error("%s: host not set; cannot enable VPN", __func__);
		return -EINVAL;
	}
	err = ov_init_data(provider, vpnhost);
	if (err < 0)
		return err;

	connman_task_add_argument(task, "--client", NULL);
	connman_task_add_argument(task, "--tls-client", NULL);
	connman_task_add_argument(task, "--remote", "%s", vpnhost);
	connman_task_add_argument(task, "--nobind", NULL);
	connman_task_add_argument(task, "--persist-key", NULL);
	connman_task_add_argument(task, "--persist-tun", NULL);

	connman_task_add_argument(task, "--dev", "%s", if_name);
	connman_task_add_argument(task, "--dev-type", "%s", "tun");
	connman_task_add_argument(task, "--syslog", NULL);

	verb = connman_provider_get_string(provider, "OpenVPN.Verb");
	if (verb == NULL && connman_debug_enabled(DBG_VPN) == TRUE)
		verb = "3";
	connman_task_add_argument(task, "--verb", "%s", verb);

	OPT_STR("VPN.MTU",		"--mtu");
	OPT_STR("OpenVPN.Proto",	"--proto");
	OPT_STR("OpenVPN.Port",		"--port");
	OPT_STR("OpenVPN.TLSAuth",	"--tls-auth");
	tls_auth_contents = connman_provider_get_string(
			provider,
			"OpenVPN.TLSAuthContents");
	if (tls_auth_contents != NULL) {
		struct openvpn_data *data = vpn_get_specific_data(provider);
		int handle;
		handle = g_file_open_tmp("tls-auth.XXXXXX",
					 &data->tls_auth_file,
					 NULL);
		if (handle < 0) {
			ov_cleanup_data(provider);
			connman_error("%s: unable to create temp tls-auth",
				      __func__);
			return -EIO;
		}
		close(handle);
		if (!g_file_set_contents(data->tls_auth_file, tls_auth_contents,
					 strlen(tls_auth_contents), NULL)) {
			ov_cleanup_data(provider);
			connman_error("%s: unable to write to tls auth file",
				      __func__);
			return -EIO;
		}
		connman_task_add_argument(task, "--tls-auth",
					  data->tls_auth_file);
	}
	OPT_STR("OpenVPN.TLSRemote",	"--tls-remote");
	OPT_STR("OpenVPN.Cipher",	"--cipher");
	OPT_STR("OpenVPN.Auth",		"--auth");
	OPT_BOOL("OpenVPN.AuthNoCache",	"--auth-nocache");
	OPT_STR("OpenVPN.AuthRetry",	"--auth-retry");
	OPT_BOOL("OpenVPN.CompLZO",	"--comp-lzo");
	OPT_BOOL("OpenVPN.CompNoAdapt",	"--comp-noadapt");
	OPT_BOOL("OpenVPN.PushPeerInfo","--push-peer-info");
	OPT_STR("OpenVPN.RenegSec",	"--reneg-sec");
	OPT_STR("OpenVPN.Shaper",	"--shaper");
	OPT_STR("OpenVPN.ServerPollTimeout", "--server-poll-timeout");

#ifdef ENABLE_NSS
	ca_cert_nss =
		connman_provider_get_string(provider, "OpenVPN.CACertNSS");
	if (ca_cert_nss != NULL) {
		char *filename;
		if (connman_provider_get_string(provider,
						"OpenVPN.CACert") != NULL) {
			connman_error("%s: CACert and CACertNSS cannot both be "
				      "specified", __func__);
			return -EIO;
		}
		filename = nss_get_pem_certfile(ca_cert_nss, (uint8_t *)vpnhost,
						strlen(vpnhost));
		if (filename != NULL) {
			connman_task_add_argument(task, "--ca",
			    "%s", filename);
		} else {
			connman_error("%s: Could not extract certificate %s",
			    __func__, ca_cert_nss);
		}
		g_free(filename);
	}
#endif

	/* client-side ping support */
	OPT_STR("OpenVPN.Ping",		"--ping");
	OPT_STR("OpenVPN.PingExit",	"--ping-exit");
	OPT_STR("OpenVPN.PingRestart",	"--ping-restart");

	OPT_STR("OpenVPN.CACert",	"--ca");
	OPT_STR("OpenVPN.Cert",		"--cert");
	OPT_STR("OpenVPN.NsCertType",	"--ns-cert-type");
	OPT_STR("OpenVPN.Key",		"--key");

	/* PKCS#11 support. */
	pkcs11_id = connman_provider_get_string(provider, "OpenVPN.Pkcs11.ID");
	if (pkcs11_id != NULL) {
		const char *pkcs11_provider;
		pkcs11_provider = connman_provider_get_string(provider,
		    "OpenVPN.Pkcs11.Provider");
		if (pkcs11_provider != NULL) {
			connman_task_add_argument(task, "--pkcs11-providers",
			    "%s", pkcs11_provider);
		} else {
#ifdef DEFAULT_PKCS11
			connman_task_add_argument(task, "--pkcs11-providers",
			    "%s", DEFAULT_PKCS11);
#endif
		}
		connman_task_add_argument(task, "--pkcs11-id", "%s", pkcs11_id);
	}

	/* TLS support */
	remote_cert_tls = connman_provider_get_string(provider,
						      "OpenVPN.RemoteCertTLS");
	if (remote_cert_tls == NULL)
		remote_cert_tls = "server";
	if (g_strcmp0(remote_cert_tls, "none") != 0)
		connman_task_add_argument(task, "--remote-cert-tls",
					  "%s", remote_cert_tls);

	/* NB: undocumented cmd line arg; works like .cfg file entry */
	/* TODO(sleffler) maybe roll into --tls-auth? */
	OPT_STR("OpenVPN.KeyDirection",	"--key-direction");
	/* TODO(sleffler) support >1 eku parameter */
	OPT_STR("OpenVPN.RemoteCertEKU","--remote-cert-eku");
	OPT_STR("OpenVPN.RemoteCertKU",	"--remote-cert-ku");

	err = add_mgmt_arguments(provider, task, vpnhost);
	if (err < 0)
		return err;

	/*
	 * Arrange for our callback script to be called with the
	 * DBus info required to send us the Layer 3 configuration;
	 * see ov_notify for our handling.
	 */
	connman_task_add_argument(task, "--setenv", NULL);
	connman_task_add_argument(task, "CONNMAN_BUSNAME",
				"%s", dbus_bus_get_unique_name(connection));

	connman_task_add_argument(task, "--setenv", NULL);
	connman_task_add_argument(task, "CONNMAN_INTERFACE",
				"%s", CONNMAN_TASK_INTERFACE);

	connman_task_add_argument(task, "--setenv", NULL);
	connman_task_add_argument(task, "CONNMAN_PATH",
				"%s", connman_task_get_path(task));

	add_platform_vars(task);

	connman_task_add_argument(task, "--script-security", "%d", 2);

	connman_task_add_argument(task, "--up",
				"%s", SCRIPTDIR "/openvpn-script");
	connman_task_add_argument(task, "--up-restart", NULL);

	/* disable openvpn handling, we do route+ifconfig work */
	connman_task_add_argument(task, "--route-noexec", NULL);
	connman_task_add_argument(task, "--ifconfig-noexec", NULL);

	/*
	 * Drop root privileges on connection and enable callback
	 * scripts to talk to the Task interface to send notify
	 * messages (after dropping privs).
	 */
	connman_task_add_argument(task, "--user", "%s", "openvpn");
	connman_task_add_argument(task, "--group", "%s", "openvpn");

	fd = fileno(stderr);
	err = connman_task_run(task, ov_vpn_died, provider,
			NULL, &fd, &fd);
	if (err < 0) {
		connman_error("%s: openvpn failed to start task (err %d)",
		    __func__, err);
		ov_cleanup_data(provider);
		return -EIO;
	}

	return 0;
}
#undef OPT_BOOL
#undef OPT_STR

static const char *ov_public_props[] = {
	"OpenVPN.Auth",
	"OpenVPN.AuthNoCache",
	"OpenVPN.AuthRetry",
	"OpenVPN.AuthUserPass",
	"OpenVPN.CACert",
	"OpenVPN.CACertNSS",
	"OpenVPN.Cert",
	"OpenVPN.Cipher",
	"OpenVPN.CompLZO",
	"OpenVPN.CompNoAdapt",
	"OpenVPN.Key",
	"OpenVPN.KeyDirection",
	"OpenVPN.NsCertType",
	"OpenVPN.Pkcs11.PIN",
	"OpenVPN.Pkcs11.Provider",
	"OpenVPN.Ping",
	"OpenVPN.PingExit",
	"OpenVPN.PingRestart",
	"OpenVPN.Port",
	"OpenVPN.Proto",
	"OpenVPN.PushPeerInfo",
	"OpenVPN.RemoteCertEKU",
	"OpenVPN.RemoteCertKU",
	"OpenVPN.RemoteCertTLS",
	"OpenVPN.RenegSec",
	"OpenVPN.Shaper",
	"OpenVPN.ServerPollTimeout",
	"OpenVPN.StaticChallenge",
	"OpenVPN.TLSAuth",
	"OpenVPN.TLSAuthContents",
	"OpenVPN.TLSRemote",
	"OpenVPN.User",
	"OpenVPN.Verb",
	"VPN.MTU",
	NULL
};


/*
 * Append plugin-specific properties to the D-Bus dictionary.
 */
void ov_append_props(struct connman_provider *provider, DBusMessageIter *iter,
    connman_bool_t isprivileged)
{
	static const char *ov_priv_props[] = {
		"OpenVPN.OTP",
		/* NB: intentionally leave out OpenVPN.Password */
		"OpenVPN.Pkcs11.ID",
		NULL
	};
	dbus_bool_t required;

	connman_provider_append_properties(provider, ov_public_props, iter);
	if (isprivileged)
		connman_provider_append_properties(provider, ov_priv_props,
		    iter);

	required = connman_provider_property_is_empty(provider,
							kPasswordProperty);
	connman_dbus_dict_append_basic(iter, "PassphraseRequired",
					DBUS_TYPE_BOOLEAN, &required);
}

/*
 * Load plugin-specific propetiers from the profile.
 */
static void ov_load_props(struct connman_provider *provider, GKeyFile *keyfile)
{
	connman_provider_load_save_properties(
	    provider, ov_public_props, connman_provider_load_property, keyfile);
	connman_provider_load_encrypted_property(provider, kPasswordProperty,
	    keyfile);
}

/*
 * Save plugin-specific properties to the profile.
 */
static void ov_save_props(struct connman_provider *provider, GKeyFile *keyfile)
{
	connman_provider_load_save_properties(
	    provider, ov_public_props, connman_provider_save_property, keyfile);
	connman_provider_save_encrypted_property(provider, kPasswordProperty,
	    keyfile);
}

static struct vpn_driver vpn_driver = {
	.notify		= ov_notify,
	.connect	= ov_connect,
	.append_props	= ov_append_props,
	.load_props	= ov_load_props,
	.save_props	= ov_save_props,
};

static void __mask_value_of_keys(void)
{
	connman_log_mask_value_of_key(kOTPProperty);
	connman_log_mask_value_of_key(kPasswordProperty);
	connman_log_mask_value_of_key(kUserProperty);
}

static int openvpn_init(void)
{
	connection = connman_dbus_get_connection();

	__mask_value_of_keys();

	return vpn_register("openvpn", &vpn_driver, OPENVPN);
}

static void openvpn_exit(void)
{
	vpn_unregister("openvpn");

	dbus_connection_unref(connection);

	free(platform_name);
	free(platform_version);
}

CONNMAN_PLUGIN_DEFINE(openvpn, "OpenVPN plugin", VERSION,
	CONNMAN_PLUGIN_PRIORITY_DEFAULT, openvpn_init, openvpn_exit)
